package msgcomm.transceivers;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import msgcomm.CrashedException;
import msgcomm.Hooks;
import msgcomm.Receiver;
import msgcomm.messages.Message;
import msgcomm.messages.MulticastMessage;
import msgcomm.messages.Payload;
import msgcomm.messages.UnicastMessage;



/**
 * Implementa un servizio di multicast senza alcun ordinamento dei messaggi.
 * Estende Unicast, quindi fornisce anche i servizi di qust'ultimo.
 */
public class BasicMulticast extends Unicast {
	
	// COSTRUTTORI
	
	protected BasicMulticast(String name, Queue<Message> msgQueue, Hooks reqList) {
		super(name, msgQueue, reqList);
	}
	
	protected BasicMulticast(String name, Queue<Message> msgQueue) {
		this(name, msgQueue, null);
	}
	
	public BasicMulticast(String name) {
		this(name, new LinkedList<Message>(), null);
	}
	
	public BasicMulticast(String name, Hooks reqList) {
		this(name, new LinkedList<Message>(), reqList);
	}

	// METODI PER IL MULTICAST
	
	/**
	 * Invia un messaggio in multicast
	 * @param dests mappa dei destinatari
	 * @param data payload del messaggio
	 * @throws CrashedException errore nel inviare il messaggio a uno o piu' destinatari 
	 */
	public void bMulticast(Map<String, Receiver> dests, Payload data) throws CrashedException{
		MulticastMessage msg = new MulticastMessage(name,data);
		bMulticast(dests, msg);
	}
	
	
	protected void bMulticast(Map<String, Receiver> dests, Message msg) throws CrashedException {
		log.printLogTh("b-multicast " + msg + ", destinatari " + dests.keySet());
		LinkedHashSet<String> destsCrashed= new LinkedHashSet<String>();
		for (Map.Entry<String, Receiver> entry : dests.entrySet()) {
			
			try {
				uniSend(entry.getKey(), entry.getValue(), msg);
			} catch (Exception e) {
				log.printErrTh("b-multicast " + entry.getKey() + " ha fatto crash, msg: " + msg);
				//e.printStackTrace(log.getErrorPrintStream());
				destsCrashed.add(entry.getKey());
			}			
		}
		
		if(!destsCrashed.isEmpty()){
			//qualcuno ha fatto crash
			log.printErrTh("b-multicast hanno fatto crash " + destsCrashed);
			throw new CrashedException(destsCrashed);
		}
	}
	
	
	@Override
	public void receive(Message msg) {
		log.printLogTh("b-receive " + msg);
		// scendo fino al transceiver base
		super.receive(msg);
	}
	
	/**
	 * Il deliver relativo al Basic Multicast, altro non e' che un deliver normale.
	 * @param msg messaggio da consegnare
	 */
	protected void bDeliver(Message msg) {
		log.printLogTh("b-deliver " + msg);
		putMessage(msg);
	}

	@Override
	protected void uniDeliver(Message msg) {
		// risalgo la catena di deliver
		log.printLogTh("uni-deliver(override) " + msg);
		if (msg instanceof UnicastMessage) {
			super.uniDeliver(msg);
		} else if (msg instanceof MulticastMessage) {
			bDeliver(msg);
		} else {
			throw new RuntimeException("Posso ricevere solo messaggi unicast e multicast");
		}
	}
}
